package jp.co.menox.tmserver.service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.slim3.datastore.Datastore;
import org.slim3.datastore.FilterCriterion;
import org.slim3.datastore.InMemoryFilterCriterion;
import org.slim3.datastore.ModelQuery;

import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;

import jp.co.menox.tmcommon.meta.CommonModelMeta;
import jp.co.menox.tmcommon.meta.LendingMeta;
import jp.co.menox.tmcommon.model.Lending;
import jp.co.menox.tmserver.dao.LendingDao;
import jp.co.menox.tmserver.dao.LendingHistoryDao;
import jp.co.menox.tmserver.searchform.SampleSearchForm;
import jp.co.menox.tmserver.searchform.bollow.LendingSearchForm;

/**
 * 貸したいものテーブル関連サービス
 * @author
 *
 */
public class LendingService extends TmServerServiceBase<Lending, LendingDao> {

    private LendingHistoryDao histDao;

    public LendingService() {
        dao = new LendingDao();
        histDao = new LendingHistoryDao();
    }

    /**
     * 更新処理：　historyに影響のある更新は不可
     * @param model
     */
    @Override
    public Lending insert(Lending model) {
        // TODO: 設計次第
        return super.insert(model);
    }

    /**
     * 更新処理：　historyに影響のある更新は不可
     * @param model
     */
    @Override
    public Lending update(Lending model) {
        // TODO: 設計次第
        return super.update(model);
    }

    /**
     * 削除処理：　使用中の場合は削除不可
     * @param model
     */
    @Override
    public Lending delete(Lending model) {
        if (model == null) {
            return null;
        }

        if (histDao.getSizeInLendingId(model.getId()) > 0) {
            // 有効なレコードが存在する
            // TODO: 独自例外を作るか。また、メッセージを定義するか。
            throw new RuntimeException("「貸し借り履歴」で使用中のため削除できません。");
        }
        return super.delete(model);
    }


    // **** 業務ロジック

    /**
     * サンプル版　：
     * 検索条件に従いLendingのリストを取得する。
     * @param searchForm 検索条件form
     * @return
     */
    public List<Lending> getLendingListWithSearchingCondition(SampleSearchForm searchForm) {

        LendingMeta m = LendingMeta.get();
        ModelQuery<Lending> query = Datastore.query(m);

        // **** 通常の検索条件
        // filterをchainしていく記法なのでAND条件で繋がる。
        // OR条件は、個別のfilter criteriaを生成し、model metaのor filter criterionを生成する。
        // 以下は、検索formに設置したfilter operatorから２つのfilter criteriaを生成し、
        // それをorで繋ぐ例。
        // 具体的な動作は、カテゴリがaかbかのいずれか、という検索。
        FilterCriterion criterionCate1 = null;
        FilterCriterion criterionCate2 = null;
        if (searchForm.getCanCate1Search() != null) {
            // 初期値のインチキ文字列FLAG_STRINGか、画面checkboxのチェック値("ON")が入っている
            criterionCate1 = this.<Lending, Long>getCriterion(
                    Query.FilterOperator.valueOf(searchForm.getCate1Filter()),
                    m.categoryId, searchForm.getCate1());
        }
        if (searchForm.getCanCate2Search() != null) {
            criterionCate2 = this.<Lending, Long>getCriterion(
                    Query.FilterOperator.valueOf(searchForm.getCate2Filter()),
                    m.categoryId, searchForm.getCate2());
        }
        if (criterionCate1 != null && criterionCate2 != null) {
            FilterCriterion criterion = m.or(criterionCate1, criterionCate2);
            query = query.filter(criterion);
        } else if (criterionCate1 != null) {
            query = query.filter(criterionCate1);
        } else if (criterionCate2 != null) {
            query = query.filter(criterionCate2);
        } else {
            // nop
        }

        // 論理削除されていないもの
        query = query.filter(
                CommonModelMeta.get().wasDeleted.getName(),
                Query.FilterOperator.EQUAL, false);

        // 並び替え（datastore側）：　検索条件次第でエラーになるのでここではやらない
//        query = query.sort(
//                CommonModelMeta.get().created.getName(),
//                SortDirection.DESCENDING);

        // ****datastore的に特殊な検索条件
        // 特殊な検索条件（文字列の部分一致、不等式フィルタを複数使用する、など）では、
        // InMemoryフィルタを使用する。
        // レコードをメモリに読み込んでから条件検索が行われる（ロジック側で書くのと同じ）ので、
        // 先に出来るだけdatastoreの検索条件で（InMemoryではないfilterで）絞っておく。

        // 名称の検索
        if (searchForm.getCanWordSearch() != null) {
            // 初期値のインチキ文字列FLAG_STRINGか、画面checkboxのチェック値("ON")が入っている
            InMemoryFilterCriterion crirerionSearchWord = this.<Lending>getCriterion(
                    searchForm.getWordMatcher(),
                    m.name, searchForm.getSearchWord());
            query = query.filterInMemory(crirerionSearchWord);
        }

        // ****並び替え（InMemory側）
        // datastore側でFilterOperator.EQUAL以外の検索条件を用いた場合には、
        // その項目を並び替えの第一項目にする必要がある。
        // 結局、汎用的には、InMemoryで並び替えをするしかない。
        // ちなみに、最後にasKey()でkeyのリストを取得する場合は、
        // InMemoryでもkey以外では並び替えられない。

        // このサンプルでは、適当にレコード作成日で並び替え
        query = query.sortInMemory(LendingMeta.get().created.desc);

        // ****レコード行範囲の取得（pager対応）
        // offset()による行切り出しは、検索条件による検索前の状態で1000行まで。
        // しかも一度1000行を読み込むのでまともには使えない。
        // また、query cursorは、InMemoryQueryを使用した段階では使えないことが多い。
        // （「IN や != フィルタを使用するクエリに対しては使用できません」）
        // 結局、簡単に先に検索条件に合致する一覧リストを取得して、対象のレコード行範囲を
        // 絞るのが良さそう。今回は気にするほどメモリを食うはずもないので。

        // 検索結果リストを取得
        searchForm.setMaximumNumber(0);
        List<Lending> modelList = query.asList();
        if (modelList == null) {
            return Collections.<Lending>emptyList();
        }

        // pager用設定
        int maximumNumber = modelList.size();
        if (maximumNumber == 0) {
            return modelList;
        }
        searchForm.setMaximumNumber(maximumNumber);

        // 表示行範囲切り出し
        int fromNumber = searchForm.getStartNumberInPage();
        int toNumber = searchForm.getEndNumberInPage();
        List<Lending> modelSubList = modelList.subList(fromNumber - 1, toNumber - 1 + 1);

        List<Lending> modelPageList = new ArrayList<Lending>(modelSubList);
        return modelPageList;
    }
    
    /**
     * 検索条件に従いLendingのリストを取得する。
     * @param searchForm 検索条件form
     * @return
     */
    public List<Lending> getLendingListWithSearchingCondition(LendingSearchForm searchForm) {

        LendingMeta m = LendingMeta.get();
        ModelQuery<Lending> query = Datastore.query(m);

        // **** 通常の検索条件

        FilterCriterion criterionCate1 = null;
        FilterCriterion criterionCate2 = null;
        if (searchForm.getCanCate1Search() != null) {
            // 初期値のインチキ文字列FLAG_STRINGか、画面checkboxのチェック値("ON")が入っている
            criterionCate1 = this.<Lending, Long>getCriterion(
                    Query.FilterOperator.valueOf(searchForm.getCate1Filter()),
                    m.categoryId, searchForm.getCate1());
        }
        if (searchForm.getCanCate2Search() != null) {
            criterionCate2 = this.<Lending, Long>getCriterion(
                    Query.FilterOperator.valueOf(searchForm.getCate2Filter()),
                    m.categoryId, searchForm.getCate2());
        }
        if (criterionCate1 != null && criterionCate2 != null) {
            FilterCriterion criterion = m.or(criterionCate1, criterionCate2);
            query = query.filter(criterion);
        } else if (criterionCate1 != null) {
            query = query.filter(criterionCate1);
        } else if (criterionCate2 != null) {
            query = query.filter(criterionCate2);
        } else {
            // nop
        }

        // 論理削除されていないもの
        query = query.filter(m.wasDeleted.equal(false));


        // ****datastore的に特殊な検索条件

        // 名称の検索
        if (searchForm.getCanWordSearch() != null) {
            // 初期値のインチキ文字列FLAG_STRINGか、画面checkboxのチェック値("ON")が入っている
            InMemoryFilterCriterion crirerionSearchWord = this.<Lending>getCriterion(
                    searchForm.getWordMatcher(),
                    m.name, searchForm.getSearchWord());
            query = query.filterInMemory(crirerionSearchWord);
        }

        // 貸出可能な期間のもの
        Calendar current = Calendar.getInstance();
        current.set(Calendar.HOUR_OF_DAY, 0);
        current.set(Calendar.MINUTE, 0);
        current.set(Calendar.SECOND, 0);
        query = query.filterInMemory(m.fromDate.lessThan(current.getTime()));
        query = query.filterInMemory(m.toDate.greaterThan(current.getTime()));
        

        // ****並び替え（InMemory側）

        // 適当にレコード作成日で並び替え
        query = query.sortInMemory(LendingMeta.get().created.desc);


        // ****レコード行範囲の取得（pager対応）

        // 検索結果リストを取得
        searchForm.setMaximumNumber(0);
        List<Lending> modelList = query.asList();
        if (modelList == null) {
            return Collections.<Lending>emptyList();
        }

        // pager用設定
        int maximumNumber = modelList.size();
        if (maximumNumber == 0) {
            return modelList;
        }
        searchForm.setMaximumNumber(maximumNumber);

        // 表示行範囲切り出し
        int fromNumber = searchForm.getStartNumberInPage();
        int toNumber = searchForm.getEndNumberInPage();
        List<Lending> modelSubList = modelList.subList(fromNumber - 1, toNumber - 1 + 1);

        List<Lending> modelPageList = new ArrayList<Lending>(modelSubList);
        return modelPageList;
    }
    
    /**
     * 該当するユーザの貸したいもの一覧を取得する
     * @param userId
     * @return
     */
    public List<Lending> getAllInUserId(long userId) {
        return dao.getAllInUserId(userId);
    }
    
}
